import contextlib
import hashlib
import json
import random
import string
import uuid
import time
from os.path import defpath
from typing import Optional, Literal, Union, Tuple

from nonebot import logger as nb_logger
from tortoise.queryset import Q

from LittlePaimon.config import config
from LittlePaimon.database import PublicCookie, PrivateCookie, CookieCache
from LittlePaimon.utils.captcha import rrocr
from LittlePaimon.utils import logger
from .requests import aiorequests

# MIHOYO_API = 'https://api-takumi-record.mihoyo.com/'
# MIHOYO_API_OLD = 'https://api-takumi.mihoyo.com/'
ABYSS_API = (
    'https://api-takumi-record.mihoyo.com/game_record/app/genshin/api/spiralAbyss'
)
HARD_CHALLENGE_API = (
    'https://api-takumi-record.mihoyo.com/game_record/app/genshin/api/hard_challenge'
)
ROLE_COMBAT_API = (
    'https://api-takumi-record.mihoyo.com/game_record/app/genshin/api/role_combat'
)
PLAYER_CARD_API = (
    'https://api-takumi-record.mihoyo.com/game_record/app/genshin/api/index'
)
CHARACTER_DETAIL_API = (
    'https://api-takumi-record.mihoyo.com/game_record/app/genshin/api/character'
)
CHARACTER_SKILL_API = (
    'https://api-takumi.mihoyo.com/event/e20200928calculate/v1/sync/avatar/detail'
)
MONTH_INFO_API = 'https://hk4e-api.mihoyo.com/event/ys_ledger/monthInfo'
DAILY_NOTE_API = (
    'https://api-takumi-record.mihoyo.com/game_record/app/genshin/api/dailyNote'
)
GAME_RECORD_API = (
    'https://api-takumi-record.mihoyo.com/game_record/card/wapi/getGameRecordCard'
)
SIGN_INFO_API = 'https://api-takumi.mihoyo.com/event/luna/info'
SIGN_REWARD_API = 'https://api-takumi.mihoyo.com/event/luna/home'
SIGN_ACTION_API = 'https://api-takumi.mihoyo.com/event/luna/sign'

AUTHKEY_API = 'https://api-takumi.mihoyo.com/binding/api/genAuthKey'
STOKEN_API = 'https://api-takumi.mihoyo.com/auth/api/getMultiTokenByLoginTicket'
COOKIE_TOKEN_API = 'https://api-takumi.mihoyo.com/auth/api/getCookieAccountInfoBySToken'

LOGIN_TICKET_INFO_API = (
    'https://webapi.account.mihoyo.com/Api/cookie_accountinfo_by_loginticket'
)


def md5(text: str) -> str:
    """
    md5加密

    :param text: 文本
    :return: md5加密后的文本
    """
    md5_ = hashlib.md5()
    md5_.update(text.encode())
    return md5_.hexdigest()


def random_hex(length: int) -> str:
    """
    生成指定长度的随机字符串

    :param length: 长度
    :return: 随机字符串
    """
    result = hex(random.randint(0, 16 ** length)).replace('0x', '').upper()
    if len(result) < length:
        result = '0' * (length - len(result)) + result
    return result


def random_text(length: int) -> str:
    """
    生成指定长度的随机字符串

    :param length: 长度
    :return: 随机字符串
    """
    return ''.join(random.sample(string.ascii_lowercase + string.digits, length))


def get_ds(q: str = '', b: dict = None, mhy_bbs_sign: bool = False) -> str:
    """
    生成米游社headers的ds_token

    :param q: 查询
    :param b: 请求体
    :param mhy_bbs_sign: 是否为米游社讨论区签到
    :return: ds_token
    """
    br = json.dumps(b) if b else ''
    if mhy_bbs_sign:
        s = 't0qEgfub6cvueAPgR5m9aQWWVciEer7v'
    else:
        s = 'xV8v4Qu54lUKrEYFZkJhB8cuOh9Asafs'
    t = str(int(time.time()))
    r = str(random.randint(100000, 200000))
    c = md5(f'salt={s}&t={t}&r={r}&b={br}&q={q}')
    return f'{t},{r},{c}'


def get_old_version_ds(mhy_bbs: bool = False) -> str:
    """
    生成米游社旧版本headers的ds_token
    """
    if mhy_bbs:
        s = '1OJyMNCqFlstEQqqMOv0rKCIdTOoJhNt'
    else:
        s = 'AcpNVhfh0oedCobdCyFV8EE1jMOVDy9q'
    t = str(int(time.time()))
    r = ''.join(random.sample(string.ascii_lowercase + string.digits, 6))
    c = md5(f"salt={s}&t={t}&r={r}")
    return f"{t},{r},{c}"


def mihoyo_headers(cookie, q='', b=None) -> dict:
    """
    生成米游社headers
        :param cookie: cookie
        :param q: 查询
        :param b: 请求体
        :return: headers
    """
    return {
        'DS': get_ds(q, b),
        'x-rpc-device_fp': '38d7f236aea34',
        'x-rpc-device_id': random_hex(32),
        'Origin': 'https://webstatic.mihoyo.com',
        'Cookie': cookie,
        'x-rpc-app_version': "2.60.1",
        'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS '
                      'X) AppleWebKit/605.1.15 (KHTML, like Gecko) miHoYoBBS/2.60.1',
        'x-rpc-client_type': '5',
        'Referer': 'https://webstatic.mihoyo.com/',
    }


def mihoyo_sign_headers(cookie: str, extra_headers: Optional[dict] = None) -> dict:
    """
    生成米游社签到headers
        :param cookie: cookie
        :param extra_headers: 额外的headers参数
        :return: headers
    """
    header = {
        'User_Agent': 'Mozilla/5.0 (Linux; Android 12; Unspecified Device) AppleWebKit/537.36 (KHTML, like Gecko) '
                      'Version/4.0 Chrome/103.0.5060.129 Mobile Safari/537.36 miHoYoBBS/2.60.1',
        'Cookie': cookie,
        'x-rpc-device_id': random_hex(32),
        'Origin': 'https://act.mihoyo.com',
        'X_Requested_With': 'com.mihoyo.hyperion',
        'DS': get_old_version_ds(mhy_bbs=True),
        'x-rpc-client_type': '5',
        'Referer': 'https://act.mihoyo.com',
        "x-rpc-signgame":"hk4e",
        'x-rpc-app_version': '2.60.1',
    }
    if extra_headers:
        header.update(extra_headers)
    return header


async def check_retcode(data: dict, cookie_info, user_id: str, uid: str) -> bool:
    """
    检查数据响应状态冰进行响应处理
        :param data: 数据
        :param cookie_info: cookie信息
        :param user_id: 用户id
        :param uid: 原神uid
        :return: 数据是否有效
    """
    if not data:
        return False
    if data['retcode'] in [10001, -100]:
        if isinstance(cookie_info, PrivateCookie):
            if cookie_info.status == 1:
                cookie_info.status = 0
                await cookie_info.save()
                logger.info('原神Cookie', f'用户<m>{user_id}</m>的私人cookie<m>{uid}</m>疑似失效')
            elif cookie_info.status == 0:
                await cookie_info.delete()
                logger.info(
                    '原神Cookie',
                    f'用户<m>{user_id}</m>的私人cookie<m>{uid}</m>连续失效，<r>已删除</r>',
                )
        elif isinstance(cookie_info, PublicCookie):
            await CookieCache.filter(cookie=cookie_info.cookie).delete()
            await cookie_info.delete()
            logger.info('原神Cookie', f'<m>{cookie_info.id}</m>号公共cookie已失效，<r>已删除</r>')
        else:
            await PublicCookie.filter(cookie=cookie_info.cookie).delete()
            await cookie_info.delete()
            logger.info(
                '原神Cookie', f'UID<m>{cookie_info.uid}</m>使用的缓存cookie已失效，<r>已删除</r>'
            )
        return False
    elif data['retcode'] == 10101:
        cookie_info.status = 2
        if isinstance(cookie_info, PrivateCookie):
            cookie_info.status = 2
            await cookie_info.save()
            logger.info(
                '原神Cookie', f'用户<m>{user_id}</m>的私人cookie<m>{uid}</m>已达到每日30次查询上限'
            )
        elif isinstance(cookie_info, PublicCookie):
            cookie_info.status = 2
            await cookie_info.save()
            logger.info('原神Cookie', f'<m>{cookie_info.id}</m>号公共cookie已达到每日30次查询上限')
        else:
            await PublicCookie.filter(cookie=cookie_info.cookie).update(status=2)
            await cookie_info.delete()
            logger.info(
                '原神Cookie', f'UID<m>{cookie_info.uid}</m>使用的缓存cookie已达到每日30次查询上限'
            )
        return False
    else:
        if isinstance(cookie_info, PublicCookie) and data['retcode'] != 1034:
            await CookieCache.update_or_create(
                uid=uid, defaults={'cookie': cookie_info.cookie}
            )
        return True


async def get_device_id(cookie: str) -> str:
    """
    使用 cookie 通过 uuid v3 生成设备 ID。
    :param cookie: cookie

    :return: 设备 ID。
    """
    return str(uuid.uuid3(uuid.NAMESPACE_URL, cookie))

async def get_cookie(
        user_id: str, uid: str, check: bool = True, own: bool = False
) -> Union[None, PrivateCookie, PublicCookie, CookieCache]:
    """
    获取可用的cookie
        :param user_id: 用户id
        :param uid: 原神uid
        :param check: 是否获取疑似失效的cookie
        :param own: 是否只获取和uid对应的cookie
    """
    query = Q(status=1) | Q(status=0) if check else Q(status=1)
    if private_cookie := await PrivateCookie.filter(
            Q(Q(query) & Q(user_id=user_id) & Q(uid=uid))
    ).first():
        return private_cookie
    elif not own:
        if cache_cookie := await CookieCache.get_or_none(uid=uid):
            return cache_cookie
        elif private_cookie := await PrivateCookie.filter(
                Q(Q(query) & Q(user_id=user_id))
        ).first():
            return private_cookie
        else:
            return await PublicCookie.filter(Q(query)).first()
    else:
        return None


async def get_bind_game_info(cookie: str, mys_id: str):
    """
    通过cookie，获取米游社绑定的原神游戏信息
    :param cookie: cookie
    :param mys_id: 米游社id
    :return: 原神信息
    """
    with contextlib.suppress(Exception):
        data = await aiorequests.get(
            url=GAME_RECORD_API,
            headers=mihoyo_headers(cookie, f'uid={mys_id}'),
            params={'uid': mys_id},
        )
        data = data.json()
        nb_logger.debug(data)
        if data['retcode'] == 0:
            return data['data']
    return None


async def get_Abyss2_info(
        uid: str,
        user_id: Optional[str],
):
    cookie_info = await get_cookie(user_id, uid, True)
    if not cookie_info:
        return '当前没有可使用的cookie，请使用命令[原神扫码绑定]/[ysb]绑定私人cookie或联系超级管理员添加公共cookie，'
    server_id = 'cn_qd01' if uid[0] == '5' else 'cn_gf01'
    headers = mihoyo_headers(
        q=f'role_id={uid}&need_detail=True&server={server_id}',
        cookie=cookie_info.cookie,
    )
    headers['x-rpc-device_fp'] = '38d7f236aea34'
    headers["x-rpc-device_id"] = await get_device_id(cookie_info.cookie)
    data: dict = (
        await aiorequests.get(
            url=HARD_CHALLENGE_API,
            headers=headers,
            params={
                "role_id": uid,
                "server": server_id,
                "need_detail": True,
            }
        )
    ).json()
    return data

async def get_abyss_info(
        uid: str,
        user_id: Optional[str],
        schedule_type: Optional[str] = "1",
):
    server_id = 'cn_qd01' if uid[0] == '5' else 'cn_gf01'
    cookie_info = await get_cookie(user_id, uid, True)
    ocr = rrocr(user_id, uid)
    if not cookie_info:
        return '当前没有可使用的cookie，请使用命令[原神扫码绑定]/[ysb]绑定私人cookie或联系超级管理员添加公共cookie，'
    headers = mihoyo_headers(
        q=f'role_id={uid}&schedule_type={schedule_type}&server={server_id}',
        cookie=cookie_info.cookie,
    )
    headers['x-rpc-device_fp'] = '38d7f236aea34'
    headers["x-rpc-device_id"] = await get_device_id(cookie_info.cookie)
    k = 0
    for i in range(4):
        if i != 0:
            logger.info('原神深渊战报', f'触发验证码，即将进行第{i}次重试，最多3次')
        data: dict = (
            await aiorequests.get(
                url=ABYSS_API,
                headers=headers,
                params={
                    "schedule_type": schedule_type,
                    "role_id": uid,
                    "server": server_id,
                },
            )
        ).json()
        if data['retcode'] == 1034 and k < 2:
            k += 1
            logger.info('原神深渊战报', '遭遇验证码，正尝试过码')
            challenge = await ocr.get_pass_challenge(cookie_info)
            if challenge is not None:
                headers.update({"challenge": challenge})
                continue
            else:
                logger.info('原神深渊战报', '过码失败')
                return "遇到验证码，但是过码失败"
        elif data['retcode'] == 0:
            logger.info('原神深渊战报', f'过码成功' if k > 0 else f"获取数据成功")
            return data
        return data['message']
#
# async def get_role_combat_info(
#         uid: str,
#         user_id: Optional[str]
# ):
#     cookie_info = await get_cookie(user_id, uid, True)
#     if not cookie_info:
#         return '当前没有可使用的cookie，请使用命令[原神扫码绑定]/[ysb]绑定私人cookie或联系超级管理员添加公共cookie，'
#     server_id = 'cn_qd01' if uid[0] == '5' else 'cn_gf01'
#     headers = mihoyo_headers(
#         q=f'role_id={uid}&need_detail=True&server={server_id}&active=1',
#         cookie=cookie_info.cookie,
# )
#     headers['x-rpc-device_fp'] = '38d7f236aea34'
#     headers["x-rpc-device_id"] = await get_device_id(cookie_info.cookie)
#     data: dict = (
#         await aiorequests.get(
#             url=ROLE_COMBAT_API,
#             headers=headers,
#             params={
#             "role_id": uid,
#             "server": server_id,
#             "active": 1,
#             "need_detail": True,
#             }
#         )
#     ).json()
#     return data

async def get_mihoyo_public_data(
        uid: str,
        user_id: Optional[str],
        mode: Literal['abyss', 'player_card', 'role_detail'],
):
    server_id = 'cn_qd01' if uid[0] == '5' else 'cn_gf01'
    check = True
    while True:
        cookie_info = await get_cookie(user_id, uid, check)
        check = False
        if not cookie_info:
            return '当前没有可使用的cookie，请使用命令[原神扫码绑定]/[ysb]绑定私人cookie或联系超级管理员添加公共cookie，'
        elif mode == 'player_card':
            data = await aiorequests.get(
                url=PLAYER_CARD_API,
                headers=mihoyo_headers(
                    q=f'role_id={uid}&server={server_id}', cookie=cookie_info.cookie
                ),
                params={'server': server_id, 'role_id': uid},
            )
        elif mode == 'role_detail':
            json_data = {"server": server_id, "role_id": uid, "character_ids": []}
            data = await aiorequests.post(
                url=CHARACTER_DETAIL_API,
                headers=mihoyo_headers(b=json_data, cookie=cookie_info.cookie),
                json=json_data,
            )
        else:
            data = None
        data = data.json() if data else {'retcode': 999}
        nb_logger.debug(data)
        if await check_retcode(data, cookie_info, user_id, uid):
            return data


async def get_mihoyo_private_data(
        uid: str,
        user_id: Optional[str],
        mode: Literal['role_skill', 'month_info', 'daily_note', 'sign_info', 'sign_action'],
        role_id: Optional[str] = None,
        month: Optional[str] = None,
):
    server_id = 'cn_qd01' if uid[0] == '5' else 'cn_gf01'
    cookie_info = await get_cookie(user_id, uid, True, True)
    if not cookie_info:
        return (
                '未绑定私人cookie，绑定方法二选一：\n1.通过米游社扫码绑定：\n请发送指令[原神扫码绑定]\n2.获取cookie的教程：\ndocs.qq.com/doc/DQ3JLWk1vQVllZ2Z1\n获取后，使用[ysb cookie]指令绑定'
                + (f'或前往{config.CookieWeb_url}网页添加绑定' if config.CookieWeb_enable else '')
        )
    if mode == 'role_skill':
        data = await aiorequests.get(
            url=CHARACTER_SKILL_API,
            headers=mihoyo_headers(
                q=f'uid={uid}&region={server_id}&avatar_id={role_id}',
                cookie=cookie_info.cookie,
            ),
            params={"region": server_id, "uid": uid, "avatar_id": role_id},
        )
    elif mode == 'month_info':
        data = await aiorequests.get(
            url=MONTH_INFO_API,
            headers=mihoyo_headers(
                q=f'month={month}&bind_uid={uid}&bind_region={server_id}',
                cookie=cookie_info.cookie,
            ),
            params={"month": month, "bind_uid": uid, "bind_region": server_id},
        )
    elif mode == 'daily_note':
        data = await aiorequests.get(
            url=DAILY_NOTE_API,
            headers=mihoyo_headers(
                q=f'role_id={uid}&server={server_id}', cookie=cookie_info.cookie
            ),
            params={"server": server_id, "role_id": uid},
        )
    elif mode == 'sign_info':
        data = await aiorequests.get(
            url=SIGN_INFO_API,
            headers={
                'x-rpc-app_version': '2.60.1',
                'x-rpc-client_type': '5',
                'Origin': 'https://act.mihoyo.com',
                'Referer': 'https://act.mihoyo.com/',
                'Cookie': cookie_info.cookie,
                'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS '
                              'X) AppleWebKit/605.1.15 (KHTML, like Gecko) miHoYoBBS/2.60.1',
            },
            params={'act_id': 'e202009291139501', 'region': server_id, 'uid': uid},
        )
    elif mode == 'sign_action':
        data = await aiorequests.post(
            url=SIGN_ACTION_API,
            headers=mihoyo_sign_headers(cookie_info.cookie),
            json={'act_id': 'e202009291139501', 'uid': uid, 'region': server_id},
        )
    else:
        data = None
    data = data.json() if data else {'retcode': 999}
    nb_logger.debug(data)
    if await check_retcode(data, cookie_info, user_id, uid):
        return data
    else:
        return f'你的UID{uid}的cookie疑似失效了'


async def get_sign_reward_list() -> dict:
    headers = {
        'x-rpc-app_version': '2.60.1',
        'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 ('
                      'KHTML, like Gecko) miHoYoBBS/2.60.1',
        "x-rpc-signgame":"hk4e",
        'Referer': 'https://act.mihoyo.com/'
    }
    resp = await aiorequests.get(
        url=SIGN_REWARD_API, headers=headers, params={'act_id': 'e202311201442471'}
    )
    data = resp.json()
    nb_logger.debug(data)
    return data


async def get_stoken_by_login_ticket(login_ticket: str, mys_id: str) -> Optional[str]:
    with contextlib.suppress(Exception):
        data = await aiorequests.get(
            STOKEN_API,
            headers={
                'x-rpc-app_version': '2.11.2',
                'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) miHoYoBBS/2.60.1',
                'x-rpc-client_type': '5',
                'Referer': 'https://webstatic.mihoyo.com/',
                'Origin': 'https://webstatic.mihoyo.com',
            },
            params={'login_ticket': login_ticket, 'token_types': '3', 'uid': mys_id},
        )
        data = data.json()
        return data['data']['list'][0]['token']
    return None


async def get_cookie_token_by_stoken(stoken: str, mys_id: str) -> Optional[str]:
    with contextlib.suppress(Exception):
        data = await aiorequests.get(
            COOKIE_TOKEN_API,
            headers={
                'x-rpc-app_version': '2.11.2',
                'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) miHoYoBBS/2.60.1',
                'x-rpc-client_type': '5',
                'Referer': 'https://webstatic.mihoyo.com/',
                'Origin': 'https://webstatic.mihoyo.com',
                'Cookie': f'stuid={mys_id};stoken={stoken}',
            },
            params={'uid': mys_id, 'stoken': stoken},
        )
        data = data.json()
        return data['data']['cookie_token']
    return None


async def get_authkey_by_stoken(
        user_id: str, uid: str
) -> Tuple[Optional[str], bool, Optional[PrivateCookie]]:
    """
    根据stoken获取authkey

    :param user_id: 用户id
    :param uid: 原神uid
    :return: authkey
    """
    server_id = 'cn_qd01' if uid[0] == '5' else 'cn_gf01'
    cookie_info = await get_cookie(user_id, uid, True, True)
    if not cookie_info:
        return (
            '未绑定私人cookie，绑定方法二选一：\n1.通过米游社扫码绑定：\n请发送指令[原神扫码绑定]\n2.获取cookie的教程：\ndocs.qq.com/doc/DQ3JLWk1vQVllZ2Z1\n获取后，使用[ysb cookie]指令绑定'
            + (f'或前往{config.CookieWeb_url}网页添加绑定' if config.CookieWeb_enable else ''),
            False,
            cookie_info,
        )
    if not cookie_info.stoken:
        return 'cookie中没有stoken字段，请重新绑定', False, cookie_info
    headers = {
        "Cookie": cookie_info.stoken,
        "DS": get_old_version_ds(True),
        "User-Agent": "okhttp/4.8.0",
        "x-rpc-app_version": "2.60.1",
        "x-rpc-sys_version": "12",
        "x-rpc-client_type": "5",
        "x-rpc-channel": "mihoyo",
        "x-rpc-device_id": random_hex(32),
        "x-rpc-device_name": random_text(random.randint(1, 10)),
        "x-rpc-device_model": "Mi 10",
        "Referer": "https://app.mihoyo.com",
        "Host": "api-takumi.mihoyo.com",
    }
    data = await aiorequests.post(
        url=AUTHKEY_API,
        headers=headers,
        json={
            'auth_appid': 'webview_gacha',
            'game_biz': 'hk4e_cn',
            'game_uid': uid,
            'region': server_id,
        },
    )
    data = data.json()
    if data.get('data') is not None and 'authkey' in data['data']:
        return data['data']['authkey'], True, cookie_info
    else:
        return None, False, cookie_info


async def get_enka_data(uid: str):
    urls = [
        'https://enka.network/api/uid/{uid}',
        'https://profile.microgg.cn/api/uid/{uid}',  # 以下两个api由小灰灰提供
        'https://enka.microgg.cn/api/uid/{uid}',
    ]
    for url in urls:
        with contextlib.suppress(Exception):
            resp = await aiorequests.get(
                url=url.format(uid=uid),
                headers={'User-Agent': 'LittlePaimon/3.0'},
                follow_redirects=True,
            )
            data = resp.json()
            nb_logger.debug(data)
            if 'playerInfo' in data:
                return data
